#ifndef __TCP_ECHO_SERVER_HPP__
#define __TCP_ECHO_SERVER_HPP__

#include "Comm.hpp"
#include "Logger.hpp"
#include "InetAddr.hpp"

#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <functional>

static const int gdefaultfd = -1;
static const int gbacklog = 8;
static const int gport = 8080;

using callback_t = std::function<std::string (const std::string&)>;


class CommandServer
{
private:

    void HandlerIO(int sockfd, InetAddr client)
    {
        char buffer[1024];
        while (true)
        {
            buffer[0] = 0;

            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1); // bug??
            if (n > 0)
            {
                buffer[n] = 0;
                LOG(Loglevel::DEBUG) << client.ToString() << "say: " << buffer;

                std::string result = _cb(buffer);


                write(sockfd, result.c_str(), result.size());
            }
            else if (n == 0)
            {
                LOG(Loglevel::INFO) << "client "
                                    << client.ToString() << " quit, me too, close fd: " << sockfd;
                break;
            }
            else
            {
                LOG(Loglevel::WARNING) << "read client "
                                       << client.ToString() << " error, sockfd : " << sockfd;
                break;
            }
        }

        close(sockfd);
    }

public:
    CommandServer(callback_t cb, uint16_t port = gport)
        : _listensockfd(gdefaultfd), _port(port), _cb(cb)
    {
    }
    void Init()
    {

        _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(Loglevel::FATAL) << "create tcp socket error";
            exit(SOCKET_CREATE_ERR);
        }
        LOG(Loglevel::INFO) << "create tcp socket success: " << _listensockfd; // 3

        // 2. bind socket fd
        InetAddr local(_port);

        if (bind(_listensockfd, local.Addr(), local.Length()) != 0)
        {
            LOG(Loglevel::FATAL) << "bind socket error";
            exit(SOCKET_BIND_ERR);
        }
        LOG(Loglevel::INFO) << "bind socket success: " << _listensockfd;


        if (listen(_listensockfd, gbacklog) != 0)
        {
            LOG(Loglevel::FATAL) << "listen socket error";
            exit(SOCKET_LISTEN_ERR);
        }
        LOG(Loglevel::INFO) << "Listen socket success: " << _listensockfd;
    }

    class ThreadData
    {
    public:
        ThreadData(int sockfd, CommandServer *self, const InetAddr &addr)
        : _sockfd(sockfd), _self(self), _addr(addr)
        {}
    public:
        int _sockfd;
        CommandServer *_self;
        InetAddr _addr;
    };

    static void *Routine(void *args)
    {
        ThreadData *td = static_cast<ThreadData*>(args);
        pthread_detach(pthread_self());
        td->_self->HandlerIO(td->_sockfd, td->_addr);
        delete td;

        return nullptr;
    }
    void Start()
    {
        // signal(SIGCHLD, SIG_IGN); // 最佳实践
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensockfd, (struct sockaddr *)&peer, &len);
            if (sockfd < 0)
            {
                LOG(Loglevel::WARNING) << "accept client error";
                continue;
            }
            InetAddr clientaddr(peer);
            LOG(Loglevel::INFO) << "获取新连接成功, sockfd is : " << sockfd
                                << " client addr: " << clientaddr.ToString();
            pthread_t tid;
            ThreadData *td = new ThreadData(sockfd, this, clientaddr);
            pthread_create(&tid, nullptr, Routine, (void*)td);
        }
    }
    ~CommandServer()
    {
    }

private:
    int _listensockfd;
    uint16_t _port;
    callback_t _cb;
};

#endif